<?php
/**
 * @author all@cosmos-framework.org
 * @copyright (c) 2013 cosmos-framework.org
 * @license http://www.opensource.org/licenses/mit-license.php
 * @link http://cosmos-framework.org/
 * @package cosmos.framework
 * @version 2.0
 */
namespace Cosmos;

class Database {

    private static $_pcommit;

    /**
     * @param string $d
     * @return boolean
     */
    public static function commit($d) {

        return self::connect($d)->commit();
    }

    /**
     * @global $_PDOENVS[ d ][ user, password, options ]
     */
    public static function & connect($d) {

        static $_connects;

        $d = self::_connect($d);

        // is connected ?
        if (!$_ = &$_connects[$d]) {

            global $_PDOENVS;

            $p[0] = &$_PDOENVS[$d][0];
            $p[1] = &$_PDOENVS[$d][1];
            $p[2] = &$_PDOENVS[$d][2];

            // response connect.
            $_ = new \PDO($d, $p[0], $p[1], $p[2]);
        }

        return $_;
    }

    /**
     * @param string $statement
     * @return int
     */
    public static function execute($statement) {

        self::_match($statement, $p);

        return self::connect($p[1])->exec($p[2]);
    }

    /**
     * @param string $statement
     * @return mixed
     */
    public static function fetch($statement) {

        self::_match($statement, $p);

        return call_user_func_array(array(self::connect($p[1]), 'query'), func_get_args());
    }

    /**
     * @param string $d
     * @param callable $f
     * @param int $s
     * @return mixed
     */
    public static function pcommit($d, $f, $s = null) {

        if (self::$_pcommit) {

            // connect setting.

            is_null($s) ? : self::timeout($d, $s);

            // /** sqlite::memory: **/ BEGIN ...

            self::execute('/** ' . $d . ' **/ BEGIN ;');
            self::$_pcommit = $d;

            try {
                $response = call_user_func($f);

                self::commit();
                self::$_pcommit = null;

                // response invoked.
                return $response;

            } catch (\PDOException $e) {
            } catch (\Exception $e) {

                self::rollback();
                self::$_pcommit = null;
            }

            // fail response.
            throw $e;
        }

        // invoke response.
        return call_user_func($f);
    }

    /**
     * @param string $module
     * @param mixed[] $fields
     * @return mixed
     */
    public static function requires($module, $fields = array()) {

        $s = SQL::requires($module, $fields);

        return preg_match('/^\s*SELECT/', $s) ? self::fetch($s) : self::execute($s);
    }

    /**
     * @param string $d
     * @return boolean
     */
    public static function rollback($d) {

        return self::connect($d)->rollBack();
    }

    /**
     * @param string $d
     * @param int $s
     * @return boolean
     */
    public static function timeout($d, $s) {

        $c = &self::connect($d);

        switch ($c->getAttribute(PDO::ATTR_DRIVER_NAME)) {

            case 'mysql' :

                // wait_timeout.
                return $c->exec('SET wait_timeout=' . $s);

            default :

                // support attr_timeout.
                return $c->setAttribute(PDO::ATTR_TIMEOUT, $s);
        }
    }

    /**
     * @access private
     */
    private static function _connect($d) {

        global $_PDOENVS;

        return self::$_pcommit ? : $d ? : $_PDOENVS ? array_rand($_PDOENVS) : null;
    }

    /**
     * @access private
     */
    private static function _match($statement, &$p) {

        Logger::debug(__CLASS__ . sprintf('(%s)', $statement));

        // /** sqlite::memory: **/ SELECT * FROM ...

        preg_match('/^\/\*\*\s*(.*?)\s*\*\*\/\s*(.*)/', $statement, $p);

        // initialize response.

        $p[1] = empty($p[1]) ? null : $p[1];
        $p[2] = empty($p[2]) ? $statement : $p[2];
    }

}
